package control;

import java.util.Vector;

import storage.ManageData;
import utils.CommandAnalyzer;
import domain.Income;
import enums.Frequency;
import basic.CRUDable;
import basic.Date;
import basic.Manager;


//@author A0098891H
public class IncomeManager extends Manager  {

	private Vector<Income> _listOfIncomes;
	private MemberManager _memMan;
	
	/**
	 * @param budget
	 * @param memMan
	 */
	public IncomeManager(MemberManager memMan)
	{
		_memMan = memMan;
		this.initialize();
	}
	/**
	 * 
	 * @return
	 * @Precondition 
	 * @Postcondition 
	 */
	public Vector<Income> getList()
	{
		return this._listOfIncomes;
	}
	
	/**
	 *
	 * @see basic.Manager#add(basic.CRUDable)
	 * @param item
	 * @return
	 * @Precondition 
	 * @Postcondition 
	 */
	@Override
	public int add(CRUDable item) {
		item.set_id(_nextId++);
		if(this._listOfIncomes.add((Income)item))
		{
			((Income) item).set_lastUpdateDate(((Income) item).get_date());
			checkRecurIncome(((Income)item));
			this._totalAmountOfAll+=((Income)item).get_totalAmount();
			return item.get_id();
		}
		else
		{
			_nextId--;
			return -1;
		}
	}
	
	/**
	 *
	 * @see basic.Manager#addTo(basic.CRUDable, int, int)
	 * @param item
	 * @param id
	 * @param pos
	 * @return
	 * @Precondition 
	 * @Postcondition 
	 */
	@Override
	public boolean addTo(CRUDable item, int id,int pos) {
		item.set_id(id);
		((Income) item).set_lastUpdateDate(((Income) item).get_lastUpdateDate());
		checkRecurIncome((Income)item);
		this._listOfIncomes.add(pos,(Income)item);
		this._totalAmountOfAll+=((Income)item).get_totalAmount();
		
		return true;
	}

	/**
	 *
	 * @see basic.Manager#get(int)
	 * @param id
	 * @return
	 * @Precondition 
	 * @Postcondition 
	 */
	@Override
	public Income get(int id) {
		for(Income c:_listOfIncomes)
		{
			if(c.get_id()==id)
				return c;
		}
		return null;
	}

	/**
	 *
	 * @see basic.Manager#edit(int, basic.CRUDable)
	 * @param id
	 * @param item
	 * @return
	 * @Precondition 
	 * @Postcondition 
	 */
	@Override
	public Income edit(int id, CRUDable item) {
		for(Income c:_listOfIncomes)
		{
			if(c.get_id()==id)
			{
				Income income = new Income(c);
				c.set((Income)item);//this casting is necessary
				if(((Income)item).get_frequency()==Frequency.STOPPED)
				{
					c.set_lastUpdateDate(income.get_lastUpdateDate());
				}
				else
				{
					c.set_lastUpdateDate(((Income)item).get_date());
				}
				c.set_totalAmount(income.get_totalAmount());
				checkRecurIncome(c);
				this._totalAmountOfAll-= income.get_totalAmount();
				this._totalAmountOfAll+= c.get_totalAmount();
				return income;
			}
		}
		return null;
	}

	/**
	 *
	 * @see basic.Manager#delete(int)
	 * @param id
	 * @return
	 * @Precondition 
	 * @Postcondition 
	 */
	@Override
	public Income delete(int id) {
		for(Income c:_listOfIncomes)
		{
			if(c.get_id()==id)
			{
				if(_listOfIncomes.remove(c))
				{
					this._totalAmountOfAll-= c.get_totalAmount();
					return c;
				}
				else
					return null;
			}
		}
		return null;
	}
	
	public int deleteFrom(int id) {
		int i=0;
		for(Income c:_listOfIncomes)
		{
			if(c.get_id()==id)
			{
				if(_listOfIncomes.remove(c))
				{
					this._totalAmountOfAll-= c.get_totalAmount();
					return i;
				}
				else
					return -1;
			}
			i++;
		}
		return -1;
	}

	/**
	 *
	 * @see interfaces.CommandLine#readCmd(java.lang.String)
	 * @param cmd
	 * @return
	 * @Precondition 
	 * @Postcondition 
	 */
	@Override
	public int readCmd(String cmd) {
		//<INCOME>|<ADD> <NAME> <AMOUNT> (COMPULSORY)
		//<INCOME>|<EDIT> <ID> <NAME> <AMOUNT> (COMPULSORY) 
		//OPTIONAL BUT NEED PARAMETER pf:<PAYER> d:<DATE> m:<MEMBER> f:<FREQUENCY> cm:<COMMENTS>
		
		//<INCOME>|<DELETE> <ID>
		//<INCOME>|<VIEW> <ID>
		//<INCOME>|<UNDO>
		//check undo
		boolean isUndo = false;
		boolean isRedo = false;
		if(cmd.trim().compareToIgnoreCase("undo")==0)
		{
			if(!_undoStack.isEmpty())
			{
				isUndo = true;
				cmd = _undoStack.pop();
				int iAction = cmd.indexOf('[');
				int isavedId = cmd.indexOf('&');
				int isavedPos = cmd.indexOf(']');
				_undoAction = Integer.parseInt(cmd.substring(0, iAction));
				_savedId = Integer.parseInt(cmd.substring(iAction+1, isavedId));
				_savedPos = Integer.parseInt(cmd.substring(isavedId+1, isavedPos));

				if(_undoAction==ACTION_DELETE || _undoAction==ACTION_EDIT)
				{
					_redoStack.add(cmd.substring(0,cmd.indexOf('|')));
					cmd = cmd.substring(cmd.indexOf('|')+1);
				}
			}
		}
		else if(cmd.trim().compareToIgnoreCase("redo")==0)
		{
			if(!_redoStack.isEmpty())
			{
				isRedo = true;
				cmd = _redoStack.pop();
				if(cmd.indexOf(']')!=-1)
				{
					int iAction = cmd.indexOf('[');
					int isavedId = cmd.indexOf('&');
					int isavedPos = cmd.indexOf(']');
					_undoAction = Integer.parseInt(cmd.substring(0, iAction));
					_savedId = Integer.parseInt(cmd.substring(iAction+1, isavedId));
					_savedPos = Integer.parseInt(cmd.substring(isavedId+1, isavedPos));
					cmd = cmd.substring(cmd.indexOf(']')+1);
				}
			}	
		}
		
		String str[] = cmd.split(",");
		for(int i=0;i<str.length;i++)
		{
			str[i] = str[i].trim();
		}
		int length = str.length;
		if(length < 2)
			return -1;

		switch(str[1].toLowerCase())
		{
		case "add":
			if(length < 4) 
				return -1;
			Income income = new Income();
			if(str[2].trim().isEmpty())
				return -1;
			income.set_name(str[2]);
			try
			{
				income.set_amount(Double.parseDouble(str[3]));
			}
			catch(NumberFormatException e)
			{
				return -1;
			}
			income.set_partyInvolved(CommandAnalyzer.getPartyInvolved(str));
			if(!CommandAnalyzer.validateDateForExpenseNIncome(str))
				return -1;
			income.set_date(CommandAnalyzer.getDate(str));
			income.set_byMem(_memMan.get(CommandAnalyzer.getMemberID(str)));
			income.set_frequency(CommandAnalyzer.getFrequency(str));
			income.set_comments(CommandAnalyzer.getComments(str));
			income.set_lastUpdateDate(CommandAnalyzer.getUpdateDate(str));
			income.set_totalAmount(CommandAnalyzer.getTotalAmount(str));

			_returnedObject = income;
			if(isUndo && _undoAction==ACTION_ADD)
			{
				int _savedPos = deleteFrom(_savedId);
				if(_savedPos >= 0)
				{//must record the position and id for redo
					_redoStack.add(-1+"["+_savedId+"&"+_savedPos+"]"+cmd);
					return _undoAction;
				}
				else
					return -1;//undo failed
			}
			if(isUndo && _undoAction==ACTION_DELETE)
			{
				if(!addTo(income,_savedId,_savedPos))
					return -1;
				return _undoAction;
			}

			if(isRedo)
			{
				if(!addTo(income,_savedId,_savedPos))
					return -1;
			}
			else
			{
				_savedId = add(income);
			}
			if(_savedId<0)	
				return -1;
			_action = ACTION_ADD;
			
			break;
		case "delete":
			if(length < 3)
				return -1;
			try
			{
				_savedId = Integer.parseInt(str[2]);
				Income deletedIncome  = get(_savedId);
				if(deletedIncome==null)
					return -1;
				_savedPos = this._listOfIncomes.indexOf(deletedIncome);
				deletedIncome = delete(_savedId);
				if(deletedIncome==null)
					return -1;
				_returnedObject = deletedIncome;
				cmd+="|income,add,"+deletedIncome.toString();
				_action = ACTION_DELETE;
			}
			catch(NumberFormatException e)
			{
				return -1;
			}
			
			break;
		case "edit":
			if(length < 5)
				return -1;
			try
			{
				_savedId = Integer.parseInt(str[2]);
				Income updatedIncome = new Income();
				if(str[3].trim().isEmpty())
					return -1;
				updatedIncome.set_name(str[3]);
				updatedIncome.set_amount(Double.parseDouble(str[4]));
				updatedIncome.set_partyInvolved(CommandAnalyzer.getPartyInvolved(str));
				if(!CommandAnalyzer.validateDateForExpenseNIncome(str))
					return -1;
				updatedIncome.set_date(CommandAnalyzer.getDate(str));
				updatedIncome.set_byMem(_memMan.get(CommandAnalyzer.getMemberID(str)));
				updatedIncome.set_frequency(CommandAnalyzer.getFrequency(str));
				updatedIncome.set_comments(CommandAnalyzer.getComments(str));
				updatedIncome.set_lastUpdateDate(CommandAnalyzer.getUpdateDate(str));
				updatedIncome.set_totalAmount(CommandAnalyzer.getTotalAmount(str));
				
				Income obsoleteIncome = edit(_savedId,updatedIncome);
				if(obsoleteIncome==null)
					return -1;

				_returnedObject = obsoleteIncome;
				if(isUndo && _undoAction==ACTION_EDIT)
				{
					get(_savedId).set_totalAmount(updatedIncome.get_totalAmount());
					get(_savedId).set_lastUpdateDate(updatedIncome.get_lastUpdateDate());
					return _undoAction;
				}
				cmd+="|income,edit,"+_savedId+","+obsoleteIncome.toString();
				_action = ACTION_EDIT;
				
			}
			catch(NumberFormatException e)
			{
				return -1;
			}
			
			break;
		default:
			return -1;
		}

		_undoStack.add(_action+"["+ _savedId +"&"+ _savedPos +"]"+cmd);

		return _action;
	}

	/**
	 *
	 * @see basic.Manager#initialize()
	 * @return
	 * @Precondition 
	 * @Postcondition 
	 */
	@Override
	public boolean initialize() {
		// TODO Auto-generated method stub
		if(loadAll()==false)
		{
			super.initialize();
			this._listOfIncomes = new Vector<Income>();
		}
		checkAllRecurIncome();
		return true;
	}

	//return false if nothing
	/**
	 * 
	 * @return
	 * @Precondition 
	 * @Postcondition 
	 */
	public boolean checkRecurIncome(Income income)
	{	//TODO
			switch(income.get_frequency()) {
			case STOPPED:
				break;
			case NO:
				income.set_totalAmount(income.get_amount());
				break;
			case DAILY:
				doDailyCheck(income);
				break;
			case WEEKLY:
				doWeeklyCheck(income);
				break;	
			case MONTHLY:
				doMonthlyCheck(income);
				break;	
			case YEARLY:
				doYearlyCheck(income);
				break;
			default:
				return false;
			}
		return true;
	}
	
	public boolean checkAllRecurIncome()
	{	//TODO
		for (Income income: this._listOfIncomes) {
			 checkRecurIncome(income);
		}
		
		return true;
	}
	
	private void doDailyCheck(Income income) {
		// TODO Auto-generated method stub
		Date curDate = Date.getCurrentDate();
		income.set_totalAmount(income.get_amount());
		income.set_lastUpdateDate(income.get_date());
		while(curDate.isGreaterOrEqual(income.get_lastUpdateDate().getRelatedDate(0, 0, 0, 1)))
		{
			income.set_lastUpdateDate(income.get_lastUpdateDate().getRelatedDate(0, 0, 0, 1));
			income.addTotalAmount(income.get_amount());
		}
	}
	
	private void doWeeklyCheck(Income income) {
		// TODO Auto-generated method stub
		Date curDate = Date.getCurrentDate();
		income.set_totalAmount(income.get_amount());
		income.set_lastUpdateDate(income.get_date());
		while(curDate.isGreaterOrEqual(income.get_lastUpdateDate().getRelatedDate(0, 0, 1, 0)))
		{
			income.set_lastUpdateDate(income.get_lastUpdateDate().getRelatedDate(0, 0, 1, 0));
			income.addTotalAmount(income.get_amount());
		}
	}
	
	private void doMonthlyCheck(Income income) {
		// TODO Auto-generated method stub	
		Date curDate = Date.getCurrentDate();
		income.set_totalAmount(income.get_amount());
		income.set_lastUpdateDate(income.get_date());
		while(curDate.isGreaterOrEqual(income.get_lastUpdateDate().getRelatedDate(0, 1, 0, 0)))
		{
			income.set_lastUpdateDate(income.get_lastUpdateDate().getRelatedDate(0, 1, 0, 0));
			income.addTotalAmount(income.get_amount());
		}
	}
	
	private void doYearlyCheck(Income income) {
		// TODO Auto-generated method stub
		Date curDate = Date.getCurrentDate();
		income.set_totalAmount(income.get_amount());
		income.set_lastUpdateDate(income.get_date());
		while(curDate.isGreaterOrEqual(income.get_lastUpdateDate().getRelatedDate(1, 0, 0, 0)))
		{
			income.set_lastUpdateDate(income.get_lastUpdateDate().getRelatedDate(1, 0, 0, 0));
			income.addTotalAmount(income.get_amount());
		}
	}
	
	//return false if nothing
	/**
	 * 
	 * @return
	 * @Precondition 
	 * @Postcondition 
	 */
	public boolean checkRecurIncomes()
	{	//TODO
		return false;
	}

	/**
	 *
	 * @see basic.Manager#saveAll()
	 * @return
	 * @Precondition 
	 * @Postcondition 
	 */
	@Override
	public boolean saveAll() {
		return ManageData.saveIncomeManagerToFile(this);
	}

	/**
	 *
	 * @see basic.Manager#loadAll()
	 * @return
	 * @Precondition 
	 * @Postcondition 
	 */
	@Override
	public boolean loadAll() {
		IncomeManager incomeManager = ManageData.loadIncomeManagerFromFile();
		if(incomeManager==null)
		{
			return false;
		}
		this.set(incomeManager);
		return true;
	}
	/**
	 * 
	 * @param incomeManager
	 * @Precondition 
	 * @Postcondition 
	 */
	public void set(IncomeManager incomeManager)
	{
		super.set(incomeManager);
		this._listOfIncomes = incomeManager._listOfIncomes;
	}
	
	public double findAmountTotal() {
		double amount = 0;
		for (Income income : this._listOfIncomes) {
			amount = amount + income.get_amount();
		}
		return amount;
	}
	
	/**
	 *
	 * @see java.lang.Object#toString()
	 * @return
	 * @Precondition 
	 * @Postcondition 
	 */
	public String toString()
	{
		StringBuilder sb = new StringBuilder();
		for(Income income : this._listOfIncomes)
		{
			sb.append("  ("+income.get_id()+") "+income.toString() + "\n");
		}
		sb.append("total income: "+this._totalAmountOfAll +"\n");
		return sb.toString();
	}

	/**
	 * 
	 * @return
	 * @Precondition 
	 * @Postcondition 
	 */
	public int size() {
		return this._listOfIncomes.size();
	}
	
	public double getExpectedIncomeByFreq(Frequency freq)
	{
		double amount = 0;
		switch(freq)
		{
		case DAILY:
			for(Income income: this._listOfIncomes)
			{
				if(income.get_frequency()==Frequency.DAILY)
					amount+=income.get_amount();
				else if(income.get_frequency()==Frequency.WEEKLY)
					amount+=income.get_amount()/Date.getCurMonthMaxWeek();
				else if(income.get_frequency()==Frequency.MONTHLY)
					amount+=income.get_amount()/Date.getCurMonthMaxDate();
				else if(income.get_frequency()==Frequency.YEARLY)
					amount+=income.get_amount()/Date.getCurYearMaxDay();
				else if(income.get_frequency()==Frequency.NO)
					amount+=income.get_amount();
				else if(income.get_frequency()==Frequency.STOPPED)
					amount+=income.get_totalAmount();
			}
			return amount;
		case WEEKLY:
			for(Income income: this._listOfIncomes)
			{
				if(income.get_frequency()==Frequency.DAILY)
					amount+=income.get_amount()*7;
				else if(income.get_frequency()==Frequency.WEEKLY)
					amount+=income.get_amount();
				else if(income.get_frequency()==Frequency.MONTHLY)
					amount+=income.get_amount()/Date.getCurMonthMaxWeek();
				else if(income.get_frequency()==Frequency.YEARLY)
					amount+=income.get_amount()/Date.getCurYearMaxWeek();
				else if(income.get_frequency()==Frequency.NO)
					amount+=income.get_amount();
				else if(income.get_frequency()==Frequency.STOPPED)
					amount+=income.get_totalAmount();
			}
			return amount;
		case MONTHLY:
			for(Income income: this._listOfIncomes)
			{
				if(income.get_frequency()==Frequency.DAILY)
					amount+=income.get_amount()*Date.getCurMonthMaxDate();
				else if(income.get_frequency()==Frequency.WEEKLY)
					amount+=income.get_amount()*Date.getCurMonthMaxWeek();
				else if(income.get_frequency()==Frequency.MONTHLY)
					amount+=income.get_amount();
				else if(income.get_frequency()==Frequency.YEARLY)
					amount+=income.get_amount()/12;
				else if(income.get_frequency()==Frequency.NO)
					amount+=income.get_amount();
				else if(income.get_frequency()==Frequency.STOPPED)
					amount+=income.get_totalAmount();
			}
			return amount;
		case YEARLY:
			for(Income income: this._listOfIncomes)
			{
				if(income.get_frequency()==Frequency.DAILY)
					amount+=income.get_amount()*Date.getCurYearMaxDay();
				else if(income.get_frequency()==Frequency.WEEKLY)
					amount+=income.get_amount()*Date.getCurYearMaxWeek();
				else if(income.get_frequency()==Frequency.MONTHLY)
					amount+=income.get_amount()*12;
				else if(income.get_frequency()==Frequency.YEARLY)
					amount+=income.get_amount();
				else if(income.get_frequency()==Frequency.NO)
					amount+=income.get_amount();
				else if(income.get_frequency()==Frequency.STOPPED)
					amount+=income.get_totalAmount();
			}
			return amount;
		default:
			break;
		}
		
		return 0;
	}
	
	//@author A0103947A
	public void deleteLentInterest (String name, double interestGained, Date startDate) {
		
//		System.out.println("In deleteLentInterest");
		Vector<Income> tempList = new Vector<Income>(_listOfIncomes);
		for (int i = 0; i < tempList.size(); i++) {
			Income inc = tempList.get(i);
			String targetName = name + " Lent Interest";
			if (inc.get_frequency() == Frequency.NO && inc.get_name().equals(targetName)
				&& inc.get_amount() == interestGained
				&& inc.get_lastUpdateDate().get_day() == startDate.get_day()) {
				delete(inc.get_id());
//				System.out.println("Deleted lent income");
			}
		}
	}
		

	public void deleteInvestmentInterest (String name, double interestGained, Date startDate) {
		
//		System.out.println("In deleteInvestmentInterest");
		Vector<Income> tempList = new Vector<Income>(_listOfIncomes);
		for (int i = 0; i < tempList.size(); i++) {
			Income inc = tempList.get(i);
			String targetName = name + " Investment Interest";

			
			if (inc.get_frequency() == Frequency.NO && inc.get_name().equals(targetName)
				&& inc.get_amount() == interestGained
				&& inc.get_lastUpdateDate().get_day() == startDate.get_day()) {
				delete(inc.get_id());
//				System.out.println("Deleted investment income");
			}
		}
	}		
}
